﻿#region Copyright (c) 2000-2008 Developer Express Inc.
/*
{*******************************************************************}
{                                                                   }
{       Developer Express .NET Component Library                    }
{       AgDataGrid                                 }
{                                                                   }
{       Copyright (c) 2000-2008 Developer Express Inc.              }
{       ALL RIGHTS RESERVED                                         }
{                                                                   }
{   The entire contents of this file is protected by U.S. and       }
{   International Copyright Laws. Unauthorized reproduction,        }
{   reverse-engineering, and distribution of all or any portion of  }
{   the code contained in this file is strictly prohibited and may  }
{   result in severe civil and criminal penalties and will be       }
{   prosecuted to the maximum extent possible under the law.        }
{                                                                   }
{   RESTRICTIONS                                                    }
{                                                                   }
{   THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES           }
{   ARE CONFIDENTIAL AND PROPRIETARY TRADE                          }
{   SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS   }
{   LICENSED TO DISTRIBUTE THE PRODUCT AND ALL ACCOMPANYING .NET    }
{   CONTROLS AS PART OF AN EXECUTABLE PROGRAM ONLY.                 }
{                                                                   }
{   THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED      }
{   FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE        }
{   COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE       }
{   AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT  }
{   AND PERMISSION FROM DEVELOPER EXPRESS INC.                      }
{                                                                   }
{   CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON       }
{   ADDITIONAL RESTRICTIONS.                                        }
{                                                                   }
{*******************************************************************}
*/
#endregion Copyright (c) 2000-2008 Developer Express Inc.

using System.Windows;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace DevExpress.AgDataGrid.Internal {
	public interface IColumnAutoWidth {
		int ActualWidth { get; set; }
		bool FixedWidth { get; }
		int MinWidth { get; }
		double Width { get; }
	}
	public interface IColumnsAutoWidth {
		object CalculatorData { get; set; }
		int ColumnCount { get; }
		int Width { get; }
		IColumnAutoWidth GetColumn(int index);
		void SetColumnsWidthOnResize(Dictionary<IColumnAutoWidth, double> newColumnsWidth);
	}
	public class ColumnsAutoWidthCalculatorState {
		public int ColumnCount;
		public List<ColumnsAutoWidthCalculatorColumnState> ColumnStates;
		public int PerformLockCount;
		public int Width;
	}
	public class ColumnsAutoWidthCalculatorColumnState {
		public bool FixedWidth;
		public int MinWidth;
		public double Width;
	}
	public class ColumnsAutoWidthCalculator {
		IColumnsAutoWidth columns;
		Dictionary<IColumnAutoWidth, double> columnWidths;
		ColumnsAutoWidthCalculatorState CalculatorState {
			get { return (ColumnsAutoWidthCalculatorState)Columns.CalculatorData; }
			set { Columns.CalculatorData = value; }
		}
		protected IColumnsAutoWidth Columns { get { return columns; } }
		protected IColumnAutoWidth this[int index] { get { return Columns.GetColumn(index); } }
		public ColumnsAutoWidthCalculator(IColumnsAutoWidth columns) {
			this.columns = columns;
		}
		public void Perform() {
			if (IsPerformLocked() || IsCalculatorStateActual()) return;
			UpdateCalculatorState();
			if (Columns.ColumnCount == 0) return;
			List<IColumnAutoWidth> columns = GetColumnList();
			int columnIndex = -1;
			int tempWidth = Columns.Width;
			InitColumnWidths();
			ExcludeFixedWidthColumns(columns, ref tempWidth, ref columnIndex);
			PerformLayout(columns, tempWidth);
			FitIntoWidth(GetColumnList(), Columns.Width);
		}
		public void Prepare() {
			for (int i = 0; i < Columns.ColumnCount; i++)
				this[i].ActualWidth = -1;
			CalculatorState = new ColumnsAutoWidthCalculatorState();
		}
		public void ResizeColumn(int columnIndex, int newColumnActualWidth) {
			if (columnIndex < 0 || columnIndex >= Columns.ColumnCount)
				throw new Exception();
			if (newColumnActualWidth < 0)
				throw new Exception();
			if (columnIndex == Columns.ColumnCount - 1) return;
			InitColumnWidths();
			List<IColumnAutoWidth> columns = GetColumnList();
			int width = Columns.Width;
			int columnMaxActualWidth = GetColumnMaxActualWidth(columns, columnIndex, width);
			newColumnActualWidth = Math.Min(newColumnActualWidth, columnMaxActualWidth);
			IColumnAutoWidth column = columns[columnIndex];
			if (column.ActualWidth == newColumnActualWidth) return;
			int prevColumnActualWidth = column.ActualWidth;
			column.ActualWidth = newColumnActualWidth;
			if (column.FixedWidth) {
				columnWidths[column] = newColumnActualWidth;
			}
			ExcludeFixedWidthColumns(columns, ref width, ref columnIndex);
			if (columnIndex != -1 && !CalcLeftColumnWidths(columns, columnIndex + 1, width)) return;
			UpdateCalculatorState();
			LockPerform(true);
			try {
				CheckColumnWidths();
			} finally {
				LockPerform(false);
			}
		}
		public void Unprepare() {
			columnWidths = new Dictionary<IColumnAutoWidth, double>();
			for (int i = 0; i < Columns.ColumnCount; i++) {
				if (this[i].ActualWidth >= 0) {
					columnWidths.Add(this[i], this[i].ActualWidth);
				}
			}
			CheckColumnWidths();
		}
		bool AdjustLeftColumnWidths(List<IColumnAutoWidth> columns, int leftColumnCount, double leftColumnsTotalWidth, int width) {
			double rightColumnsTotalWidth = GetColumnsTotalWidth(columns, leftColumnCount, columns.Count - 1);
			double columnsTotalWidth = leftColumnsTotalWidth + rightColumnsTotalWidth;
			for (int i = 0; i < leftColumnCount; i++)
				columnWidths[columns[i]] = columnsTotalWidth * columns[i].ActualWidth / width;
			return true;
		}
		int CalcColumnWidth(IColumnAutoWidth column, ref int columnsTotalWidth, ref int width) {
			int columnWidth = (int)(((long)columnsTotalWidth * column.ActualWidth + width - 1) / width);
			int compare = Math.Sign((long)width * columnWidth / columnsTotalWidth - column.ActualWidth);
			if (compare == 0) {
				columnWidths[column] = columnWidth;
				width -= column.ActualWidth;
				columnsTotalWidth -= columnWidth;
			}
			return compare;
		}
		bool CalcLeftColumnWidths(List<IColumnAutoWidth> columns, int leftColumnCount, int width) {
			int leftColumnsTotalActualWidth = GetColumnsTotalActualWidth(columns, 0, leftColumnCount - 1);
			List<IColumnAutoWidth> rightColumns = new List<IColumnAutoWidth>();
			int i = leftColumnCount;
			while (i < columns.Count) {
				rightColumns.Add(columns[i]);
				columns.RemoveAt(i);
			}
			PerformLayout(rightColumns, width - leftColumnsTotalActualWidth);
			int rightColumnsTotalActualWidth = GetColumnsTotalActualWidth(rightColumns, 0, rightColumns.Count - 1);
			double rightColumnsTotalWidth = GetColumnsTotalWidth(rightColumns, 0, rightColumns.Count - 1);
			columns.AddRange(rightColumns);
			double leftColumnsTotalWidth = leftColumnsTotalActualWidth * rightColumnsTotalWidth / rightColumnsTotalActualWidth;
			return AdjustLeftColumnWidths(columns, leftColumnCount, leftColumnsTotalWidth, leftColumnsTotalActualWidth + rightColumnsTotalActualWidth);
		}
		bool CheckColumnActualWidths(List<IColumnAutoWidth> columns, ref int width) {
			bool result = true;
			for (int i = columns.Count - 1; i >= 0; i--) {
				IColumnAutoWidth column = columns[i];
				if (column.ActualWidth < column.MinWidth) {
					column.ActualWidth = column.MinWidth;
					width -= column.ActualWidth;
					columns.RemoveAt(i);
					result = false;
				}
			}
			return result;
		}
		void CheckColumnWidths() {
			Dictionary<IColumnAutoWidth, double> resizedColumnInfos = new Dictionary<IColumnAutoWidth, double>();
			for (int i = 0; i < Columns.ColumnCount; i++) {
				IColumnAutoWidth column = this[i];
				if (columnWidths[column] != column.Width) {
					resizedColumnInfos.Add(column, columnWidths[column]);
				}
			}
			Columns.SetColumnsWidthOnResize(resizedColumnInfos);
		}
		void ExcludeFixedWidthColumns(List<IColumnAutoWidth> columns, ref int width, ref int columnIndex) {
			for (int i = columns.Count - 1; i >= 0; i--) {
				IColumnAutoWidth column = columns[i];
				if (column.FixedWidth) {
					column.ActualWidth = (int)columnWidths[column];
					width -= column.ActualWidth;
					if (columnIndex >= i) {
						columnIndex--;
					}
					columns.RemoveAt(i);
				}
			}
		}
		void FitIntoWidth(List<IColumnAutoWidth> columns, int width) {
			int columnsTotalActualWidth = GetColumnsTotalActualWidth(columns, 0, columns.Count - 1);
			int totalDelta = width - columnsTotalActualWidth;
			if (totalDelta == 0) return;
			IColumnAutoWidth column;
			for (int i = 0; i <= columns.Count - 2; i++) {
				column = columns[i];
				int delta = (int)((long)totalDelta * column.ActualWidth / columnsTotalActualWidth);
				totalDelta -= delta;
				columnsTotalActualWidth -= column.ActualWidth;
				column.ActualWidth = column.ActualWidth + delta;
			}
			column = columns[columns.Count - 1];
			column.ActualWidth = column.ActualWidth + totalDelta;
		}
		List<IColumnAutoWidth> GetColumnList() {
			List<IColumnAutoWidth> result = new List<IColumnAutoWidth>();
			for (int i = 0; i < Columns.ColumnCount; i++) {
				result.Add(this[i]);
			}
			return result;
		}
		int GetColumnMaxActualWidth(List<IColumnAutoWidth> columns, int columnIndex, int width) {
			int leftColumnsTotalActualWidth = GetColumnsTotalActualWidth(columns, 0, columnIndex - 1);
			int result = width - leftColumnsTotalActualWidth;
			for (int i = columnIndex + 1; i <= columns.Count - 1; i++) {
				IColumnAutoWidth column = columns[i];
				if (column.FixedWidth) {
					result -= (int)columnWidths[column];
				} else {
					result -= column.MinWidth;
				}
			}
			return result;
		}
		int GetColumnsTotalActualWidth(List<IColumnAutoWidth> columns, int firstIndex, int lastIndex) {
			int result = 0;
			for (int i = firstIndex; i <= lastIndex; i++)
				result += columns[i].ActualWidth;
			return result;
		}
		double GetColumnsTotalWidth(List<IColumnAutoWidth> columns, int firstIndex, int lastIndex) {
			double result = 0;
			for (int i = firstIndex; i <= lastIndex; i++)
				result += columnWidths[columns[i]];
			return result;
		}
		void InitColumnWidths() {
			columnWidths = new Dictionary<IColumnAutoWidth, double>();
			for (int i = 0; i < Columns.ColumnCount; i++) {
				columnWidths.Add(this[i], this[i].Width);
			}
		}
		bool IsCalculatorStateActual() {
			if (CalculatorState.ColumnCount != Columns.ColumnCount || CalculatorState.Width != Columns.Width)
				return false;
			for (int i = 0; i < Columns.ColumnCount; i++) {
				ColumnsAutoWidthCalculatorColumnState columnState = CalculatorState.ColumnStates[i];
				IColumnAutoWidth column = this[i];
				if (columnState.FixedWidth != column.FixedWidth || columnState.MinWidth != column.MinWidth || columnState.Width != column.Width)
					return false;
			}
			return true;
		}
		bool IsPerformLocked() {
			return CalculatorState.PerformLockCount > 0;
		}
		void LockPerform(bool lockPerform) {
			if (lockPerform)
				CalculatorState.PerformLockCount++;
			else
				CalculatorState.PerformLockCount--;
		}
		void PerformLayout(List<IColumnAutoWidth> columns, int width) {
			if (columns.Count == 0) return;
			double columnsTotalWidth = GetColumnsTotalWidth(columns, 0, columns.Count - 1);
			if (columnsTotalWidth <= 0)
				throw new Exception();
			double[] newColumnActualWidths = new double[columns.Count];
			Dictionary<int, double> newColumnActualWidthRests = new Dictionary<int, double>();
			for (int i = 0; i < columns.Count; i++) {
				IColumnAutoWidth column = columns[i];
				newColumnActualWidths[i] = width * columnWidths[column] / columnsTotalWidth;
				newColumnActualWidthRests.Add(i, newColumnActualWidths[i] - Math.Floor(newColumnActualWidths[i]));
			}
			IOrderedEnumerable<KeyValuePair<int, double>> sortedNewColumnActualWidthRests = newColumnActualWidthRests.OrderByDescending(element => element.Value);
			int newColumnsTotalActualWidth = 0;
			foreach (double newColumnActualWidth in newColumnActualWidths)
				newColumnsTotalActualWidth += (int)newColumnActualWidth;
			IEnumerator<KeyValuePair<int, double>> enumerator = sortedNewColumnActualWidthRests.GetEnumerator();
			for (int i = 0; i < width - newColumnsTotalActualWidth; i++) {
				enumerator.MoveNext();
				newColumnActualWidths[enumerator.Current.Key] += 1;
			}
			for (int i = 0; i < columns.Count; i++)
				columns[i].ActualWidth = (int)newColumnActualWidths[i];
			if (!CheckColumnActualWidths(columns, ref width)) {
				PerformLayout(columns, width);
			}
		}
		void UpdateCalculatorState() {
			CalculatorState.ColumnCount = Columns.ColumnCount;
			CalculatorState.Width = Columns.Width;
			CalculatorState.ColumnStates = new List<ColumnsAutoWidthCalculatorColumnState>();
			for (int i = 0; i < Columns.ColumnCount; i++) {
				ColumnsAutoWidthCalculatorColumnState columnState = new ColumnsAutoWidthCalculatorColumnState();
				IColumnAutoWidth column = this[i];
				columnState.FixedWidth = column.FixedWidth;
				columnState.MinWidth = column.MinWidth;
				columnState.Width = column.Width;
				CalculatorState.ColumnStates.Add(columnState);
			}
		}
	}
}
